import { ref, reactive, nextTick } from 'vue'
import { preview } from 'v-preview-image'
import { jumpHttp, isIos, isMobile, isWeixinMini } from '../utils/host'
import { addTokenToUrl } from '../utils/token'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import qs from 'qs'
import { getTransportStatus, newEvaluate } from '../service/api'
import scrollTop from '../utils/scrollTop'

import MarkdownIt from 'markdown-it'
import hljs from 'highlight.js'
import 'highlight.js/styles/github.css'
const md = new MarkdownIt({
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return (
          '<pre class="hljs"><code>' +
          hljs.highlight(lang, str, true).value +
          '</code></pre>'
        )
      } catch (__) {}
    }

    return (
      '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'
    )
  }
})
const flashingLineNode = document.createElement('span')
flashingLineNode.className = 'flashing-line'

const addFlashingLineHTML = (htmlString) => {
  const parser = new DOMParser()
  const doc = parser.parseFromString(
    `<div>${htmlString.replaceAll(/\n/g, '')}</div>`,
    'text/html'
  )
  const node = doc.body.firstChild
  const lastNode = getLastTextNode(node)
  lastNode.parentNode.appendChild(flashingLineNode)
  return node.innerHTML
}
const getLastTextNode = (node) => {
  if (node.nodeType === Node.TEXT_NODE) {
    return node
  }
  for (let i = node.childNodes.length - 1; i >= 0; i--) {
    const child = node.childNodes[i]
    const lastNode = getLastTextNode(child)
    if (lastNode) {
      return lastNode
    }
  }
}
export const useCommonCheckbox = (
  proxy,
  sessionId,
  playVoiceAndAnimate,
  stopVoiceAndAnimate,
  Emit
) => {
  const config = proxy.$config
  const chatBox = ref() // 聊天框ref
  const messages = ref([]) // 消息列表
  const isAnswering = ref(false) // 是否在回答问题
  const isPending = ref(false) // 问题发送之后，出答案之前
  const processObj = ref({
    jtype: null,
    link: '',
    appendToken: 0
  }) // 跳转事务对象
  // 检测前置异常
  const testTransportStatus = () => {
    getTransportStatus().then((res) => {
      if (res.transportStatus === 1) {
        pushPreAbnormalMessage(res.transportErrorMsg)
      }
    })
  }

  // 富文本转成文本，去掉html标签
  const getSimpleText = (html) => {
    const re1 = new RegExp('<.+?>', 'g') // 匹配html标签的正则表达式，"g"是搜索匹配多个符合的内容
    const arrEntities = {
      lt: '<',
      gt: '>',
      nbsp: ' ',
      amp: '&',
      quot: '"',
      ldquo: '“',
      mdash: '—',
      rdquo: '”'
    }
    return html
      .replace(re1, '')
      .replace(
        /&(lt|gt|nbsp|amp|quot|ldquo|mdash|rdquo);/gi,
        function (all, t) {
          return arrEntities[t]
        }
      )
  }

  // 发送问题
  const send = (askObj) => {
    if (isPending.value) {
      return
    }
    stopVoiceAndAnimate()
    if (askObj.type !== 3) {
      // type:3 上传图片
      messages.value.push({
        type: 'User',
        message: getSimpleText(askObj.data.name),
        isLoading: false
      })
    }
    newGetAnswer(askObj)
  }

  // 发送input框的问题
  const sendInput = (val) => {
    // type = 2: 语音 文字输入
    if (val) {
      send({
        data: {
          name: val
        },
        type: 2
      })
    }
  }

  // 滚到底部
  const scroll = () => {
    if (chatBox.value) {
      nextTick(() => {
        scrollTop(chatBox.value, 20)
      })
    }
  }

  // 是否显示客服
  const continueNoanswerTimes = ref(0) // 连续没有答案的次数，第三次需要显示 人工客服跳转链接
  const checkShowKefu = () => {
    if (!config.manServiceUrl) {
      // 没有配置人工客服地址
    } else {
      setTimeout(() => {
        messages.value.push({
          message: '',
          type: 'Kefu',
          isLoading: false
        })
        scroll()
      }, 200)
    }
  }

  // 前置异常消息
  const pushPreAbnormalMessage = (
    message = '检测到您所在的学校前置异常，已帮您通知，工程师正快马加鞭为您解决中~请耐心等待服务恢复。'
  ) => {
    messages.value.push({
      type: 'PreAbnormal',
      message,
      isLoading: false
    })
  }

  // 新版获取答案
  let controller = null
  let voiceBuffer = '' // 语音16进制  用于实时语音流播放
  let voiceBufferEnd = true // 接口的语音流是否结束
  let voiceAllStream = '' // 获取到的语音流叠加  用于ios  pc端 不只支持 实时语音流播放 全部获取二进制语音或者主动中端消息之后 转成 ObjectURL 点击播放
  const newGetAnswer = (askObj) => {
    isPending.value = true
    let reamSteam = '' // 用于实现丝滑播放流文字
    const answerR = reactive({
      name: askObj.data.name,
      type: 'Bot', // 消息类型
      isLoading: true, // 加载中，收到接口消息 就变成 false
      answer: '', // 原始答案
      message: '', // 用于页面显示（处理过的答案）
      uuid: '', // 消息唯一标识
      isTextEnd: false, // 接口返回文字流是否结束
      stype: null, // 消息类型，
      isActive: true, // 当前正在处理的消息
      showInfo: false, // 播放完毕显示其他信息
      needUploadImg: false, // 需要上传图片
      list: [], // 推荐功能列表 跳转
      table: {}, // 表格数据
      btns: [], // 按钮
      opsStatus: '0', // 点赞点踩状态
      hrefList: [], // type =7 返回的 跳转链接数组
      chatType: '', // 回答接口返回的，用于点赞点踩
      src: '', // 音频文件地址
      isError: false, // 是否是系统异常的答案
      actionType: 'normal', // normal  welcome  byebye
      isHistory: false
    })
    messages.value.forEach((item) => {
      item.isActive = false
    })
    messages.value.push(answerR)
    scroll()
    let BASEURL = ''
    if (import.meta.env.MODE.trim() === 'development') {
      BASEURL = '/apis/virtualhuman'
    } else {
      BASEURL = '/virtualhuman2'
    }
    const options = {
      queue: [], // 控制流式播放文字
      isFirstBuffer: true, // 是否是第一个音频流
      isFirstUsefulBuffer: true, // 是否是第一个可以播放的组合音频流
      textTimer: setInterval(() => {
        const item = options.queue.shift()
        if (item) {
          reamSteam = reamSteam + item
          answerR.answer = reamSteam

          // 添加输入光标代码 跟 答案里面的代码块冲突 所以取消输入光标
          // answerR.message = addFlashingLineHTML(
          //   md.render(reamSteam).replaceAll('&lt;br&gt;', '<br />')
          // )
          answerR.message = md
            .render(reamSteam)
            .replaceAll('&lt;br&gt;', '<br />')
          scroll()
        }
        if (answerR.isTextEnd && options.queue.length === 0) {
          // answerR.message = md
          //   .render(reamSteam)
          //   .replaceAll('&lt;br&gt;', '<br />')
          if (isIos()) {
            stopVoiceAndAnimate()
            isAnswering.value = false
            jumpByProcessID()
          } else {
            if (processObj.value.link) {
              jumpByProcessID()
            }
          }
          clearInterval(options.textTimer)
          setTimeout(() => {
            answerR.showInfo = true
            scroll()
          }, 300)
        }
      }, 20)
    }

    controller = new AbortController()
    const signal = controller.signal
    // 主动中端流
    signal.addEventListener('abort', () => {
      isAnswering.value = false
      isPending.value = false
      answerR.isTextEnd = true
      answerR.showInfo = true
      answerR.message = md
        .render(answerR.answer)
        .replaceAll('&lt;br&gt;', '<br />')
      if (isIos()) {
        answerR.src = toObjectURL(voiceAllStream)
      } else {
        if (window.player) {
          window.player.stop()
        }
      }
      clearInterval(options.textTimer)
      clearInterval(options.speechTimer)
      scroll()
    })

    voiceAllStream = ''
    resetProcess()
    fetchEventSource(`${BASEURL}/serverApi/question/streamAnswer`, {
      method: 'POST',
      headers: {
        'Blade-Auth': 'bearer ' + window.access_token,
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      signal,
      openWhenHidden: true,
      body: qs.stringify({
        ask: askObj.data.name,
        sessionId: sessionId.value || config.sessionId,
        timestamp: new Date().getTime()
      }),
      onmessage(res) {
        isPending.value = false
        const ret = JSON.parse(res.data)
        if (ret.code === 200) {
          const data = ret.data
          if (data.chatType && data.finish !== 1 && data.finish !== 11) {
            answerR.chatType = data.chatType
          }
          continueNoanswerTimes.value = 0
          newExecAnswer(answerR, data, options)
        } else {
          executeErrorMessage(answerR)
        }
      },
      onerror(err) {
        executeErrorMessage(answerR)
        throw err
      }
    })
  }
  const newExecAnswer = (answerObj, data, options) => {
    if (data.list) {
      answerObj.list = data.list
    }
    if (data.btns) {
      answerObj.btns = data.btns
    }
    if (data.tr) {
      answerObj.table.th = data.th
      answerObj.table.tr = data.tr
    }
    if (data.finish === 1) {
      // 结束标识
      if (data.type === 11) {
        voiceBufferEnd = true
        // 语音结束
        if (!isIos() && voiceBuffer) {
          // 修复语音播放不完整的问题（最后一小段没播放）
          const ab = hexStringToByteArray(voiceBuffer)
          player.receiveAudioData(ab)
          voiceBuffer = ''
        }
        if (isIos()) {
          answerObj.src = toObjectURL(voiceAllStream)
        }
      } else {
        // 文字结束
        answerObj.isTextEnd = true
        isAnswering.value = false
      }
    } else if (data.type === 11) {
      voiceAllStream = voiceAllStream + data.content
      if (!isIos()) {
        if (options.isFirstBuffer) {
          voiceBufferEnd = false
          // 控制语音播放的定时器
          options.speechTimer = setInterval(() => {
            const delta =
              player.sourceBuffer.timestampOffset - player.audio.currentTime
            if (voiceBufferEnd) {
              if (delta < 0.4) {
                clearInterval(options.speechTimer)
                player.cb()
              }
            }
          }, 500)
        }

        voiceBuffer = voiceBuffer + data.content
        if (voiceBuffer.length > 15000) {
          const ab = hexStringToByteArray(voiceBuffer)
          player.receiveAudioData(ab)
          voiceBuffer = ''
          if (options.isFirstUsefulBuffer) {
            playVoiceAndAnimate('OneAnswer')
            options.isFirstUsefulBuffer = false
          }
        }
        options.isFirstBuffer = false
      }
    } else if (data.type === 1) {
      // 大模型返回
      answerObj.stype = 1
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
    } else if (data.type === 2) {
      // 普通业务办理
      answerObj.stype = 2
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
      answerObj.isTextEnd = true
      isAnswering.value = false // 没有语音
    } else if (data.type === 3) {
      // 跳转业务办理
      answerObj.stype = 3
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
      answerObj.isTextEnd = true
      if (data.subappConfig && data.subappConfig.link) {
        processObj.value.jtype = data.subappConfig.type
        processObj.value.link = !isMobile()
          ? data.subappConfig.pcLink
          : data.subappConfig.link
        processObj.value.appendToken = data.subappConfig.appendToken
        processObj.value.putTokenName =
          data.subappConfig.putTokenName || 'token'
        processObj.value.gainTokenName =
          data.subappConfig.gainTokenName || 'token'
      }
    } else if (data.type === 4) {
      // 返回带有文档来源
      answerObj.stype = 4
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
      if (data.shardingInformation) {
        if (answerObj.isLoading) {
          answerObj.quote = data.shardingInformation
        }
      }
    } else if (data.type === 5) {
      // faq
      answerObj.stype = 5
      if (data.actionType === 'welcome') {
        answerObj.actionType = 'welcome'
      } else if (data.actionType === 'byebye') {
        answerObj.actionType = 'byebye'
      }
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
      // faq类型增加来源
      if (data.shardingInformation) {
        if (answerObj.isLoading) {
          answerObj.quote = data.shardingInformation
        }
      }
    } else if (data.type === 6) {
      // 表格
      answerObj.stype = 6
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
      answerObj.isTextEnd = true
      isAnswering.value = false // 没有语音
    } else if (data.type === 7) {
      // 返回的content内容是 可点击跳转的链接(集团客服 钉钉知识库 需求)
      answerObj.stype = 7
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
      if (data.shardingInformation) {
        answerObj.hrefList = data.shardingInformation
      }
    } else if (data.type === 8) {
      // 智能问数
      answerObj.stype = 8
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
    } else if (data.type === 21) {
      // 需要上传图片
      answerObj.stype = 21
      answerObj.needUploadImg = true
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
      answerObj.isTextEnd = true
      isAnswering.value = false // 没有语音
    } else if (data.type === 22) {
      // 按钮
      answerObj.stype = 22
      Array.from(data.content).forEach((i) => {
        options.queue.push(i)
      })
      answerObj.isTextEnd = true
      isAnswering.value = false
    } else if (data.type === 31) {
      // 智能助手业务
      answerObj.stype = 31
      Array.from('当前环境不支持此类型的业务').forEach((i) => {
        options.queue.push(i)
      })
      answerObj.isTextEnd = true
    } else if (data.type === 999) {
      answerObj.uuid = data.uuid
    }
    answerObj.isLoading = false
    scroll()
  }
  const executeErrorMessage = (answerR) => {
    let animationType = ''
    isAnswering.value = false
    isPending.value = false
    answerR.isTextEnd = true
    controller.abort()
    answerR.isLoading = false
    continueNoanswerTimes.value++
    answerR.isError = true
    switch (continueNoanswerTimes.value) {
      case 1:
        answerR.message = config.apologyHint
        animationType = 'NoAnswer1'
        break
      case 2:
        if (config.apologyHintSecond) {
          answerR.message = config.apologyHintSecond
        } else {
          answerR.message = config.apologyHint
        }
        animationType = 'NoAnswer2'
        break
      case 3:
        if (config.apologyHintThree) {
          answerR.message = config.apologyHintThree
        } else {
          answerR.message = config.apologyHint
        }
        animationType = 'NoAnswer3'
        checkShowKefu()
        continueNoanswerTimes.value = 0
        break
    }
    playVoiceAndAnimate(animationType, answerR.message)
  }
  const resetProcess = () => {
    processObj.value.jtype = null
    processObj.value.link = ''
    processObj.value.appendToken = 0
  }
  const jumpByProcessID = () => {
    if (processObj.value.link) {
      if (processObj.value.jtype === 4) {
        if (!isMobile()) {
          proxy.$toast('请在移动端使用此功能')
          return
        }
        setTimeout(() => {
          window.location.href = processObj.value.link
        }, 1000)
      } else if (processObj.value.jtype === 2) {
        if (!isWeixinMini()) {
          proxy.$toast('请在微信小程序环境使用此功能')
          return
        }
        wx.miniProgram.navigateTo({
          url: processObj.value.link
        })
      } else if (processObj.value.jtype === 3) {
        const url =
          processObj.value.appendToken === 1
            ? addTokenToUrl(
                processObj.value.link,
                processObj.value.putTokenName,
                processObj.value.gainTokenName
              )
            : processObj.value.link
        setTimeout(() => {
          if (url === 'invalid url') {
            proxy.$toast('地址解析失败，请检查配置')
            return
          }
          jumpHttp(url)
        }, 1000)
      }
    }
  }

  // 停止生成
  const stopAi = () => {
    if (window.player) {
      window.player.stop()
    }
    if (Emit) {
      Emit('playVoice', '')
    }
    if (isAnswering.value) {
      playVoiceAndAnimate('Standby')
    }
    if (controller) {
      controller.abort()
    }
  }

  // 16进制转字节数组
  const hexStringToByteArray = (hexString) => {
    const byteArray = []
    for (let i = 0; i < hexString.length; i += 2) {
      byteArray.push(parseInt(hexString.substring(i, i + 2), 16))
    }
    return new Uint8Array(byteArray)
  }

  // 修复ios scroll滚动失效bug
  const fixScroll = () => {
    const scrollTop = chatBox.value.scrollTop
    if (scrollTop <= 0 || scrollTop > chatBox.value.offsetHeight) {
      document.body.style.overflow = 'hidden'
    } else {
      document.body.style.overflow = 'auto'
    }
  }

  // 文档引用展示逻辑
  const showRefPop = ref(false)
  const refContent = ref('')
  const showRef = (e) => {
    showRefPop.value = true
    refContent.value = md.render(e)
    setTimeout(() => {
      document.getElementById('refpopInfo').scrollTop = 0
    })
  }

  // 图片预览
  const showImgPreview = (e) => {
    preview(e.index, e.imgs)
  }

  // 点赞、点踩弹窗
  const feedType = ref('')
  const evaluteValue = ref('')
  const showEvalutePop = ref(false)
  const evaluteObj = ref()
  const showEvalute = (e) => {
    if (e.status === '1') {
      newEvaluate(
        {
          question: e.ask,
          ask: e.question,
          content: '',
          status: '1',
          feedType: '',
          chatType: e.chatType,
          uuid: e.uuid, // 唯一标识
          sessionId: sessionId.value || config.sessionId
        },
        true
      )
        .then(() => {
          messages.value[e.index].opsStatus = '1'
        })
        .catch((err) => {
          console.log(err)
        })
    } else {
      showEvalutePop.value = true
      evaluteObj.value = e
    }
  }
  const evalute = () => {
    if (!evaluteValue.value) {
      proxy.$toast('请填写详细问题描述')
      return
    }
    newEvaluate(
      {
        question: evaluteObj.value.ask,
        ask: evaluteObj.value.question,
        content: evaluteValue.value,
        status: '2',
        chatType: evaluteObj.value.chatType,
        feedType: feedType.value,
        uuid: evaluteObj.value.uuid, // 唯一标识
        sessionId: sessionId.value || config.sessionId
      },
      true
    )
      .then(() => {
        messages.value[evaluteObj.value.index].opsStatus = '2'
        evaluteValue.value = ''
        showEvalutePop.value = false
      })
      .catch((err) => {
        console.log(err)
      })
  }
  const changeOpsStatus = (e) => {
    messages.value[evaluteObj.value.index].opsStatus = e
  }

  return {
    chatBox,
    messages,
    isPending,
    isAnswering,
    jumpByProcessID,
    processObj,
    send,
    sendInput,
    scroll,
    fixScroll,
    testTransportStatus,
    checkShowKefu,
    showRef,
    showRefPop,
    evaluteValue,
    showEvalute,
    showEvalutePop,
    feedType,
    evalute,
    changeOpsStatus,
    refContent,
    showImgPreview,
    stopAi
  }
}
